﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Cpuss.Strategies.Properties;

namespace Cpuss.Strategies
{

    /// <summary>
    /// Round-Robin strategy.
    /// </summary>
    /// <remarks>
    /// <para>
    /// Executes each process for at most the defined time quantum. If the <see cref="Process"/> has not voluntarily
    /// released the CPU within the time quantum then it is added to the back of the ready queue.
    /// </para>
    /// <para>
    /// 80% of the CPU bursts should be less than the selected time quantum. If you select a quantum of 1 then you emulate processor
    /// sharing, in this case n processors will appear to have their own processor running at 1/n the speed of the real processor.
    /// </para>
    /// <para>
    /// This framework is not a physical implementation of course, but when analysing the metrics gathered you should take into account the
    /// quantum selected as it can adversly affect the strategies performance.
    /// </para>
    /// </remarks>
    public class RoundRobin : IStrategy
    {

        private readonly int _quantum;
        private ProcessLoad _processLoad;
        private Queue<Process> _readyQueue;

        /// <summary>
        /// Creates and initializes a new instance of <see cref="RoundRobin"/> with a defined time quantum.
        /// </summary>
        /// <param name="quantum">Time quantum.</param>
        public RoundRobin(int quantum)
        {
            if (quantum < 1)
            {
                throw new ArgumentOutOfRangeException(Resources.TimeQuantumLessThanOne);
            }
            _quantum = quantum;
            _readyQueue = new Queue<Process>();
        }

        /// <summary>
        /// Executes the processes in <see cref="Runner.ProcessLoad"/> in Round-Robin order.
        /// </summary>
        /// <param name="runner">Runner to use as execution enviroment.</param>
        public void Execute(Runner runner)
        {
            _processLoad = new ProcessLoad(runner.ProcessLoad);
            while (_processLoad.Count > 0)
            {
                List<Process> validProcesses = _processLoad.Where(x => x.ArrivalTime <= runner.Time).ToList();
                if (validProcesses.Count > 0)
                {
                    foreach (Process p in validProcesses)
                    {
                        _readyQueue.Enqueue(p);
                        _processLoad.Remove(p);
                    }
                    while (_readyQueue.Count > 0)
                    {
                        Process p = _readyQueue.Dequeue();
                        int utilized = 0;
                        while (utilized < _quantum)
                        {
                            runner.UtilizeCpu(p);
                            utilized++;
                            foreach (Process proc in _processLoad.Where(x => x.ArrivalTime <= runner.Time).ToList())
                            {
                                _readyQueue.Enqueue(proc);
                                _processLoad.Remove(proc);
                            }
                            if (Process.IsComplete(p))
                            {
                                break;
                            }
                        }
                        if (Process.IsComplete(p))
                        {
                            runner.LogProcessMetrics(p);
                        }
                        else
                        {
                            _readyQueue.Enqueue(p);
                        }
                    }
                }
                else
                {
                    runner.SkipIdleCpuTime();
                }
            }
        }

        /// <summary>
        /// Returns a <see cref="string"/> that represents the current <see cref="object"/>.
        /// </summary>
        /// <returns>String representation of object.</returns>
        public override string ToString()
        {
            return string.Format(CultureInfo.InvariantCulture, "Round Robin (Time Quantum = {0})", _quantum);
        }

    }

}
